#ifndef __BOOPT_H__
#define __BOOPT_H__

#include <iostream>
#include <iomanip>
#include <sstream>
#include <unordered_map>
#include <unordered_set>
#include <algorithm>
#include <vector>
#include <tuple>
#include <map>

namespace boopt {

namespace detail {

typedef bool (*trimfunc) (char);

inline static bool notSpace(char ch) {
    return !std::isspace(static_cast<unsigned char>(ch));
}

inline static void ltrim(std::string& is, trimfunc tf = notSpace) {
    is.erase(begin(is), std::find_if(begin(is), end(is), tf));
}

inline static void rtrim(std::string& is, trimfunc tf = notSpace) {
    is.erase(std::find_if(is.rbegin(), is.rend(), tf).base(), end(is));
}

inline static void trim(std::string& is, trimfunc tf = notSpace) {
    ltrim(is, tf);
    rtrim(is, tf);
}

inline static std::string ltrimCopy(std::string is, trimfunc tf = notSpace) {
    ltrim(is, tf);
    return is;
}

inline static std::string rtrimCopy(std::string is, trimfunc tf = notSpace) {
    rtrim(is, tf);
    return is;
}

inline static std::string trimCopy(std::string is, trimfunc tf = notSpace) {
    ltrim(is, tf);
    rtrim(is, tf);
    return is;
}

template <typename InputIt>
static inline std::string concatStrings(InputIt begin, InputIt end,
                                const std::string &separator = " ")
{
    std::ostringstream ss;
    if (begin != end)
    {
        ss << *begin++;
    }
    while (begin != end)
    {
        ss << separator;
        ss << *begin++;
    }
    return ss.str();
}

namespace inner {
template <typename T>
struct is_vector : std::false_type {};

template <typename... Args>
struct is_vector<std::vector<Args...>> : std::true_type {};
}

template <typename T>
struct is_vector {
    static constexpr bool const value =
        inner::is_vector<typename std::decay<T>::type>::value;
};


} // end of detail

enum TermColour
{
    BLACK = 30,
    MAROON = 31,
    GREEN = 32,
    BROWN = 33,
    NAVY = 34,
    PURPLE = 35,
    TEAL = 36,
    LIGHT_GRAY = 37,
    DARK_GRAY = -30,
    RED = -31,
    LIME = -32,
    YELLOW = -33,
    BLUE = -34,
    FUCHSIA = -35,
    CYAN = -36,
    WHITE = -37
};

class Colour;
Colour operator<<(std::ostream &os, TermColour colour);
class Colour
{
    friend Colour operator<<(std::ostream &os, TermColour colour);
    std::ostream &os_;

public:
    Colour(std::ostream &os, TermColour colour)
        : os_(os)
    {
        os << "\033[";
        if (colour < 0)
        {
            colour = static_cast<TermColour>(-colour);
            os_ << "1;";
        }
        os_ << static_cast<int>(colour) << 'm';
    }
    ~Colour()
    {
        os_ << "\033[0m";
    }
    operator std::ostream &() const
    {
        return os_;
    }
    template <typename T>
    std::ostream &operator<<(T &&arg)
    {
        return os_ << std::forward<T>(arg);
    }
};

Colour operator<<(std::ostream &os, TermColour colour)
{ return {os, colour}; }

struct Error
{
    friend std::ostream& operator<<(std::ostream &os, Error const& e)
    {
        (void)e;
        return os << RED << "ERROR: ";
    }
};

inline Error error_()
{ return {}; }

struct Suggest
{
    std::string const& what;
    friend std::ostream& operator<<(std::ostream &os, Suggest const& obj)
    {
        os << "; did you mean: \"";
        os << WHITE << obj.what;
        os << "\"?";
        return os;
    }
};

inline Suggest suggest_(std::string const& str)
{ return { str }; }

template <typename T>
struct Ignoring
{
    T const& arg;
    friend std::ostream& operator<<(std::ostream& os, Ignoring const& obj)
    {
        os << "; ignoring \"";
        os << WHITE << obj.arg;
        os << "\"";
        return os;
    }
};
template <typename T>
inline Ignoring<T> ignore_(T const& arg)
{ return { arg }; }


struct Names
{
    std::string n_;
    std::string l_;
    bool hasL_{false};
    Names() {}
    Names(const std::string& n, const std::string& l = "")
        : n_(n), l_(l), hasL_(l_.length() > 0)
    {}
    explicit operator std::string() const {
        return hasL_ ? n_ + ", " + l_ : n_;
    }
    bool operator==(const std::string& other) const {
        return n_ == other || l_ == other;
    }
    bool operator==(const Names& other) const {
        return n_ == other.n_ && l_ == other.l_;
    }
    bool operator<(const Names& other) const {
        return hasL_ ? (l_ < other.l_) : (n_ < other.n_);
    }
    size_t length() const {
        return hasL_ ? l_.length() : n_.length();
    }
};

struct Argument {
    enum Pos : int { LAST = -1, ANYWHERE = -2 };
    enum Cnt : int { ANY = -1 };
    int pos_{Pos::ANYWHERE};
    int cnt_{1};
    int ind_{-1};
    bool found_{false};
    bool required_{false};
    Names names_{}; // short name, long name
    std::string desc_{};
    std::vector<std::string> values_;
public:
    Argument& name(const Names& name) {
        names_ = name; return *this;
    }
    Argument& longName(const std::string& lname) {
        names_.l_ = lname; return *this;
    }
    Argument& desc(const std::string& desc) {
        desc_ = desc; return *this;
    }
    Argument& required(bool req) {
        required_ = req; return *this;
    }
    Argument& position(int pos) {
        if (pos != Pos::LAST) {
            pos_ = pos + 1;
        } else {
            pos_ = pos;
        }
        return *this;
    }
    Argument& count(int cnt) {
        cnt_ = cnt; return *this;
    }
    bool isFound() const { return found_; }

    Argument() {}
    Argument(const std::string& name, const std::string& desc, const bool req = false)
        : names_{name, ""}, desc_(desc), required_(req)
    {}
    Argument(const Names& names, const std::string& desc, const bool req = false)
        : names_(names), desc_(desc), required_(req)
    {}
    Argument(const Argument&) = default;
    Argument(Argument&&) = default;
    Argument& operator=(const Argument&) = default;
    Argument& operator=(Argument&&) = default;
    bool operator==(const Argument& other) const {
        return names_ == other.names_;
    }


    template <typename T>
    typename std::enable_if<detail::is_vector<T>::value, T>::type get()
    {
        T t = T();
        typename T::value_type vecValue;
        for (auto& v : values_)
        {
            std::stringstream in(v);
            in >> vecValue;
            t.push_back(vecValue);
        }
        return t;
    }

    template <typename T>
    typename std::enable_if<!detail::is_vector<T>::value, T>::type get()
    {
        std::stringstream in(get<std::string>());
        T t = T();
        in >> t >> std::ws;
        return t;
    }
};

struct Result
{
    std::tuple<bool, int> ret_;
    operator bool() const { return std::get<0>(ret_); }
    explicit operator int() { return std::get<1>(ret_); }
    explicit Result(std::tuple<bool, int>&& ret)
        : ret_(ret)
    {}
    explicit Result(bool found, int ind)
        : ret_(found, ind)
    {}
};

class Parser {

    std::string appName_{};
    std::string desc_{};
    std::vector<Argument> arguments_{};
    std::unordered_map<int, int> posArgs_{};
    std::map<Names, size_t> nameMap_{};
    const int argc;
    const char** argv;
    std::stringstream errMsg_;

public:
    Parser(const int ac, const char* av[], const std::string& desc)
        : appName_(av[0]), desc_(desc), argc(ac), argv(av)
    {}
    Argument& addArg(const std::string& name,
                     const std::string& desc,
                     const bool req = false)
    {
        if (exists(name))
        {
            std::cerr << error_() << name << " already in arguments list\n";
            throw std::invalid_argument(name);
        }
        arguments_.emplace_back(name, desc, req);
        arguments_.back().ind_ = static_cast<int>(arguments_.size()) - 1;
        nameMap_.emplace(Names{name}, arguments_.back().ind_);
        return arguments_.back();
    }

    Argument& addArg(const Names& names,
                     const std::string& desc,
                     const bool req = false)
    {
        if (exists(names))
        {
            std::cerr << error_() << std::string(names) << " already in arguments list\n";
            throw std::invalid_argument(std::string(names));
        }
        arguments_.emplace_back(names, desc, req);
        arguments_.back().ind_ = static_cast<int>(arguments_.size()) - 1;
        nameMap_.emplace(names, arguments_.back().ind_);
        return arguments_.back();
    }

    void usage()
    {
        std::cout << "Usage: " << appName_;
        if (posArgs_.empty())
        {
            std::cout << " [Options...]" << std::endl;
        }
        else
        {
            int curArg = 1;
            for (const auto& arg : posArgs_)
            {
                if (arg.first == Argument::Pos::LAST)
                    continue;
                for (; curArg < arg.first; ++curArg)
                {
                    std::cout << " [" << curArg << "]";
                }
                std::cout << " ["
                          << detail::ltrimCopy(
                              arguments_.at(arg.second).names_.n_,
                              [](char c) -> bool {
                                  return static_cast<unsigned char>(c) != '-'; })
                          <<  "]";
            }
            auto iter = posArgs_.find(Argument::Pos::LAST);
            if (iter == posArgs_.end())
            {
                std::cout << " [options...]";
            }
            else
            {
                std::cout << " [options...] ["
                          << detail::ltrimCopy(
                              arguments_.at(iter->second).names_.n_,
                              [](char c) -> bool {
                                  return static_cast<unsigned char>(c) != '-';})
                          << "]";
            }
            std::cout << std::endl;
        }
        std::cout << "Options: \n";
        for (size_t i = 0; i < arguments_.size(); ++i)
        {
            Argument& a = arguments_.at(i);
            std::string name = static_cast<std::string>(a.names_);
            std::cout << "\t" << std::setw(23) << std::left
                      << name << std::setw(23) << a.desc_;
            if (a.required_)
            {
                std::cout << " [Required]";
            }
            std::cout << std::endl;
        }
    }


    void processParsing()
    {
        try
        {
            markExistingArgs();
            checkRequiredArgs();
        }
        catch (std::exception& e)
        {
            std::cerr << e.what() << std::endl;
            usage();
            exit(1);
        }
    }

    template <typename T>
    T get(const std::string& name)
    {
        auto info = exists(name);
        if (info)
        {
            return arguments_[static_cast<size_t>(info)].get<T>();
        }
        return T();
    }

private:

    Result exists(const std::string& argName)
    {
        auto name = detail::ltrimCopy(
            argName, [](char c) -> bool { return static_cast<u_char>(c) != '-'; });
        for (auto iter = nameMap_.cbegin(); iter != nameMap_.cend(); ++iter)
        {
            if (iter->first == argName)
            {
                return Result(true, iter->second);
            }
        }
        return Result(false, -1);
    }

    Result exists(const Names& argName)
    {
        return exists(argName.n_);
    }

    void addValuesToArg(size_t ind, int& startArgInd)
    {
        if (startArgInd + 1 >= argc)
        {
            errMsg_ << error_() << std::string(arguments_[ind].names_)
                    << " at wrong positon[" << startArgInd << "]; "
                    << "Requires a value, but got none.";
            throw std::invalid_argument(errMsg_.str());
        }

        auto curValueCnt = arguments_[ind].values_.size();
        while (++startArgInd < argc && argv[startArgInd][0] != '-')
        {
            arguments_[ind].values_.emplace_back(argv[startArgInd]);
        }
        if (arguments_[ind].values_.size() <= curValueCnt)
        {
            errMsg_ << error_() << std::string(arguments_[ind].names_)
                    << " expect value but not given!";
            throw std::invalid_argument(errMsg_.str());
        }
        if (arguments_[ind].values_.size() > arguments_[ind].cnt_)
        {
            errMsg_ << error_() << std::string(arguments_[ind].names_)
                    << " too many arguments given!";
            throw std::invalid_argument(errMsg_.str());
        }
    }

    void markExistingArgs()
    {
        for (int argi = 1; argi < argc; ++argi)
        {
            auto info = exists(argv[argi]);
            if (info)
            {
                size_t ind = static_cast<int>(info);
                auto& found = arguments_[ind].found_;
                if (!found || arguments_[ind].cnt_ == Argument::Cnt::ANY)
                {
                    found = true;
                    addValuesToArg(ind, argi);
                }
                else
                {
                    errMsg_ << error_() << argv[argi] << " is duplicate arg\n";
                    throw std::invalid_argument(errMsg_.str());
                }
            }
            else
            {
                errMsg_ << error_() << argv[argi] << " is not valid arg\n";
                throw std::invalid_argument(errMsg_.str());
            }
            if (argi < argc && argv[argi][0] == '-')
            {
                argi--;
            }
        }
    }

    void checkRequiredArgs()
    {
        std::vector<Names> missingArgs;
        for (auto& args : arguments_)
        {
            if (args.required_ && !args.isFound())
            {
                missingArgs.emplace_back(std::move(args.names_));
            }
        }
        if (missingArgs.empty())
        {
            return ;
        }
        errMsg_ << error_() << "Arguments required but missing:\n";
        for (auto& missed : missingArgs)
        {
            errMsg_ << "\t" << std::string(missed) << std::endl;
        }
        throw std::invalid_argument(errMsg_.str());
    }

}; //end class Parser

template <>
inline std::string Argument::get<std::string>()
{
    return detail::concatStrings(values_.begin(), values_.end());
}

template <>
inline std::vector<std::string> Argument::get<std::vector<std::string>>()
{
    return values_;
}

} //end of boopt

#endif // __BOOPT_H__